/*
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *  http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package org.skatiger.jrove.component;

import java.io.IOException;
import java.util.Iterator;
import java.util.List;
import java.util.Map;

import javax.el.ValueExpression;

import org.skatiger.jrove.JroveException;
import org.skatiger.jrove.ViewContext;
import org.skatiger.jrove.renderer.Renderer;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

public abstract class UIComponentBase extends UIComponent {
    private static final Logger logger = LoggerFactory
            .getLogger(UIComponentBase.class);
    private static final ThreadLocal<StringBuilder> _STRING_BUILDER = new ThreadLocal<StringBuilder>();

    private static final Iterator<UIComponent> _EMPTY_UICOMPONENT_ITERATOR = new _EmptyIterator<UIComponent>();

    private _ComponentAttributesMap _attributesMap = null;
    private List<UIComponent> _childrenList = null;
    private Map<String, UIComponent> _facetMap = null;

    private String _id = null;
    private UIComponent _parent = null;

    private transient ViewContext _viewContext;
    private static final boolean DEFAULT_RENDERED = true;
    private String _rendererType = null;
    private Boolean _rendered = null;
    private String _clientId = null;

    public UIComponentBase() {
    }

    @Override
    protected void prepareEncode(ViewContext context) {
        super.prepareEncode(context);
        this._clientId = null;
    }

    @Override
    public void encodeBegin(ViewContext context) throws IOException {
        try {
            setCachedContext(context);
            if (!isRendered()) {
                return;
            }
            Renderer renderer = getRenderer(context);
            if (renderer != null) {
                renderer.encodeBegin(_viewContext, this);
            }
        } catch (Exception ex) {
            logger.error("encodeBegin exception", ex);
            throw new JroveException(
                    "Exception while calling encodeBegin on component : "
                            + _ComponentUtils.getPathToComponent(this), ex);
        } finally {
            setCachedContext(null);
        }
    }

    protected final <T> T getPropertyValue(String propertyName, T propertyValue) {
        return getPropertyValue(propertyName, propertyValue, null);
    }

    protected final <T> T getPropertyValue(ViewContext context,
            String propertyName, T propertyValue) {
        return getPropertyValue(context, propertyName, propertyValue, null);
    }

    /**
     * get property value wrap method
     * @param <T>
     * @param propertyName property's name
     * @param propertyValue property's value
     * @param defaultValue if property's value is null,will return default value
     * @return
     */
    protected final <T> T getPropertyValue(String propertyName,
            T propertyValue, T defaultValue) {
        return getPropertyValue(getViewContext(), propertyName, propertyValue,
                defaultValue);
    }

    /**
     * get property value wrap method
     * @param <T>
     * @param propertyName property's name
     * @param propertyValue property's value
     * @param defaultValue if property's value is null,will return default value
     * @return
     */
    @SuppressWarnings("unchecked")
    protected final <T> T getPropertyValue(ViewContext context,
            String propertyName, T propertyValue, T defaultValue) {
        if (propertyValue != null) {
            return propertyValue;
        }
        ValueExpression vb = getValueExpression(propertyName);
        if (vb != null) {
            return (T) vb.getValue(context.getELContext());
        }
        return defaultValue;
    }

    @Override
    public void encodeChildren(ViewContext context) throws IOException {

        boolean isCachedContext = isCachedContext();
        try {
            if (!isCachedContext) {
                setCachedContext(context);
            }
            if (!isRendered())
                return;
            Renderer renderer = getRenderer(context);
            if (renderer != null) {
                renderer.encodeChildren(context, this);
            }
        } finally {
            if (!isCachedContext) {
                setCachedContext(null);
            }
        }
    }

    @Override
    public void encodeEnd(ViewContext context) throws IOException {
        try {
            setCachedContext(context);
            if (!isRendered()) {
                return;
            }
            Renderer renderer = getRenderer(context);
            if (renderer != null) {
                renderer.encodeEnd(_viewContext, this);
            }
        } catch (Exception ex) {
            logger.error("encodeEnd exception.", ex);

            throw new JroveException(
                    "Exception while calling encodeEnd on component : "
                            + _ComponentUtils.getPathToComponent(this), ex);
        } finally {
            setCachedContext(null);
        }
    }

    @Override
    public Map<String, Object> getAttributes() {
        if (_attributesMap == null) {
            _attributesMap = new _ComponentAttributesMap(this);
        }
        return _attributesMap;
    }

    @Override
    public int getChildCount() {
        return _childrenList == null ? 0 : _childrenList.size();
    }

    @Override
    public List<UIComponent> getChildren() {
        if (_childrenList == null) {
            _childrenList = new _ComponentChildrenList(this);
        }
        return _childrenList;
    }

    @SuppressWarnings("unchecked")
    protected <T> T getExpressionValue(String attribute, T explizitValue,
            T defaultValueIfExpressionNull) {
        if (explizitValue != null) {
            return explizitValue;
        }
        ValueExpression ve = getValueExpression(attribute);
        if (ve != null) {
            return (T) ve.getValue(getViewContext().getELContext());
        }
        return defaultValueIfExpressionNull;
    }

    @Override
    public UIComponent getFacet(String name) {
        return _facetMap == null ? null : _facetMap.get(name);
    }

    @Override
    public int getFacetCount() {
        return _facetMap == null ? 0 : _facetMap.size();
    }

    @Override
    public Map<String, UIComponent> getFacets() {
        if (_facetMap == null) {
            _facetMap = new _ComponentFacetMap(this);
        }
        return _facetMap;
    }

    @Override
    public Iterator<UIComponent> getFacetsAndChildren() {
        if (getFacetCount() == 0) {
            if (getChildCount() == 0) {
                return _EMPTY_UICOMPONENT_ITERATOR;
            }
            return getChildren().iterator();
        } else {
            if (getChildCount() == 0) {
                return getFacets().values().iterator();
            }

            return new _FacetsAndChildrenIterator(getFacets(), getChildren());
        }
    }

    @Override
    public String getClientId(ViewContext context) {
        if (context == null)
            throw new NullPointerException("context");
        if (_clientId != null) {
            return _clientId;
        }
        String id = getId();
        if (id == null) {
            UIViewRoot viewRoot = context.getViewRoot();
            if (viewRoot != null) {
                id = viewRoot.createUniqueId();
            } else {
                // The RI throws a NPE
                throw new JroveException(
                        "Cannot create clientId. No id is assigned for component to create an id and UIViewRoot is not defined: "
                                + _ComponentUtils.getPathToComponent(this));
            }
            setId(id);
        }

        UIComponent namingContainer = _ComponentUtils
                .findParentNamingContainer(this, false);
        if (namingContainer != null) {
            String containerClientId = namingContainer
                    .getContainerClientId(context);
            if (containerClientId != null) {
                StringBuilder bld = __getSharedStringBuilder();
                _clientId = bld.append(containerClientId)
                        .append(NamingContainer.SEPARATOR_CHAR).append(id)
                        .toString();
            } else {
                _clientId = id;
            }
        } else {
            _clientId = id;
        }

        Renderer renderer = getRenderer(context);
        if (renderer != null) {
            _clientId = renderer.convertClientId(context, _clientId);
        }
        return _clientId;
    }

    @Override
    public String getId() {
        return _id;
    }

    @Override
    public UIComponent getParent() {
        return _parent;
    }

    @Override
    public void setRendererType(String rendererType) {
        this._rendererType = rendererType;
    }

    @Override
    public String getRendererType() {
        String __rendererType = getPropertyValue("rendererType", null);
        if (__rendererType == null) {
            __rendererType = this._rendererType;
        }
        return __rendererType;
    }

    public Renderer getRenderer(ViewContext context) {
        return context.getRenderer(getRendererType());
    }

    @Override
    public boolean getRendersChildren() {
        Renderer renderer = getRenderer(getViewContext());
        return renderer != null ? renderer.isRendersChildren() : false;
    }

    protected ViewContext getViewContext() {

        if (this._viewContext != null) {
            return this._viewContext;
        }

        return ViewContext.getCurrentInstance();
    }

    boolean isCachedContext() {
        return _viewContext != null;
    }

    /**
     * A boolean value that indicates whether this component should be rendered.
     * Default value: true.
     **/
    @Override
    public boolean isRendered() {
        return getExpressionValue("rendered", _rendered, DEFAULT_RENDERED);
    }

    void setCachedContext(ViewContext viewContext) {
        _viewContext = viewContext;
    }

    @Override
    public void setId(String id) {
        if (this._id == null || !(this._id.equals(id))) {
            validId(id);
            _id = id;
        }
        //        this._clientId = null;
    }

    private void validId(String string) {
        if (string == null) {
            return;
        }
        // Component identifiers must obey the following syntax restrictions:
        // 1. Must not be a zero-length String.
        if (string.length() == 0) {
            throw new IllegalArgumentException(
                    "component identifier must not be a zero-length String");
        }
    }

    @Override
    public void setParent(UIComponent parent) {
        _parent = parent;
    }

    @Override
    public void setRendered(boolean rendered) {
        _rendered = Boolean.valueOf(rendered);
    }

    protected static StringBuilder __getSharedStringBuilder() {
        StringBuilder sb = _STRING_BUILDER.get();
        if (sb == null) {
            sb = new StringBuilder();
            _STRING_BUILDER.set(sb);
        }

        // clear out the stringBuilder by setting the length to 0
        sb.setLength(0);

        return sb;
    }
}
